package com.simpou.commons.utils.interceptor;

import com.simpou.commons.utils.string.Strings;

import java.lang.reflect.Method;


public final class LogInterceptor extends AbstractInterceptor {
    /**
     * Mensagem padrão em caso de lançamento de exceções.
     */
    static final String ON_ERROR_MSG = ", throws an exception {0} with message \"{1}\"";

    /**
     * Mensagem padrão para invocações de métodos.
     */
    static final String ON_CALL_MSG = "Method \"{0}.{1}\" called with arguments: {2}";

    /**
     * Mensagem padrão para invocações de métodos.
     */
    static final String NO_ARGS_MSG = "Method \"{0}.{1}\" called with no arguments";

    /**
     * Mensagem padrão para após a invocação de método.
     */
    static final String ON_RETURN_MSG = " and returns \"{0}\"";

    /**
     * Logger.
     */
    private final Logger logger;
    private String msgSum;

    /**
     * @param logger Logger.
     */
    private LogInterceptor(final Logger logger) {
        this.logger = logger;
    }

    /**
     *
     * @param <E> Tipo do objeto a ser interceptado.
     * @param <T> Tipo da interface do objeto.
     * @param ifaceClass Classe da interface do objeto.
     * @param obj Objeto a ser interceptado.
     * @param logger Logger a ser utilizado para gravar as mensagens.
     * @return Objeto interceptado.
     */
    public static <T, E extends T> T intercept(final Class<T> ifaceClass,
        final E obj, final Logger logger) {
        final Interceptor interceptor = new LogInterceptor(logger);
        final T intercepted = interceptor.intercept(ifaceClass, obj);

        return intercepted;
    }

    @Override
    public Object doOnError(final Throwable throwable)
        throws Throwable {
        final String exMsg;
        final Throwable redirect;

        if ((throwable.getMessage() == null) && (throwable.getCause() != null)) {
            exMsg = throwable.getCause().getMessage();
            redirect = throwable.getCause();
        } else {
            exMsg = throwable.getMessage();
            redirect = throwable;
        }

        msgSum += Strings.replaceParams(ON_ERROR_MSG,
            redirect.getClass().getName(), exMsg);
        throw redirect;
    }

    @Override
    public void doBefore(final Method method, final Object[] args) {
        final String msg;

        if (args == null) {
            msg = Strings.replaceParams(NO_ARGS_MSG,
                    method.getDeclaringClass().getSimpleName(), method.getName());
        } else {
            msg = Strings.replaceParams(ON_CALL_MSG,
                    method.getDeclaringClass().getSimpleName(),
                    method.getName(), argsToString(args));
        }

        msgSum = msg;
    }

    /**
     * @param args Array de argumentos.
     * @return String representativa do array.
     */
    private String argsToString(final Object[] args) {
        StringBuilder builder = new StringBuilder("[");
        builder.append((args[0] == null) ? "null" : args[0].toString());

        for (int i = 1; i < args.length; i++) {
            builder.append(", ")
                   .append((args[i] == null) ? "null" : args[i].toString());
        }

        builder.append("]");

        return builder.toString();
    }

    @Override
    public void doAfter(final Object result) {
        msgSum += Strings.replaceParams(ON_RETURN_MSG, result);
        logger.write(msgSum);
    }

    /**
     * Executor dos logs.
     */
    public interface Logger {
        /**
         * Grava log.
         *
         * @param msg Mensagem.
         */
        void write(String msg);
    }
}
